package server;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class WriterThread extends Thread {

    private int port;
    private List<String> dirsToWrite;

    public WriterThread(int port, List<String> dirs) {
        this.port = port;
        this.dirsToWrite = dirs;
    }

    private void writeToDiskInternal(SocketChannel sc, String dirToWrite, Lock lock)
            throws IOException {
        ByteBuffer bytes = ByteBuffer.allocateDirect(32 * 1024 * 1024);
        while (true) {
            lock.lock();
            if (sc.read(bytes) < 0) {
                lock.unlock();
                return;
            }
            lock.unlock();
            bytes.flip();
            String fileName = dirToWrite + "/" + UUID.randomUUID().toString();
            FileOutputStream fileOutputStream = new FileOutputStream(fileName);
            FileChannel fileChannel = fileOutputStream.getChannel();
            fileChannel.write(bytes);
            bytes.clear();
            fileChannel.close();
            fileOutputStream.close();
        }
    }

    private void writeToDisk(final SocketChannel sc, final String fileName,
            final Lock lock) {
        for (final String dir : dirsToWrite) {
            final String dirToWrite = dir + "/" + fileName;

            // Make the directory to write to if it does not exist.
            if (!new File(dirToWrite).exists()) {
                new File(dirToWrite).mkdirs();
            }

            // Spawn threads to write file.
            (new Thread() {
                @Override
                public void run() {
                    try {
                        writeToDiskInternal(sc, dirToWrite, lock);
                    } catch (IOException ex) {
                        System.err.println("Failed to write to disk ...");
                    }
                }
            }).start();
        }
    }

    @Override
    public void run() {
        try {
            ServerSocketChannel ss = ServerSocketChannel.open();
            ss.socket().bind(new InetSocketAddress(this.port));

            while (true) {
                try {
                    SocketChannel sc = ss.accept();
                    System.out.println("Accepted a new connection ...");

                    // Reads the first 8 bytes as the filename.
                    ByteBuffer bytes = ByteBuffer.allocateDirect(8);
                    sc.read(bytes);
                    bytes.flip();

                    // Converts the file name to bytes.
                    byte[] fileName = new byte[bytes.remaining()];
                    bytes.get(fileName);

                    // Write the file received onto disk in a stream manner.
                    writeToDisk(sc, new String(fileName), new ReentrantLock());
                } catch (IOException ex) {
                    System.err.println("Failed to handle a connection ...");
                }
            }
        } catch (IOException ex) {
            System.err.println("Something went wrong ...");
        }
    }
}
